package edu.uestc.gene.entity;

import java.math.BigDecimal;
import java.util.List;

import ANOVA.ANOVA;

import com.mathworks.toolbox.javabuilder.MWArray;
import com.mathworks.toolbox.javabuilder.MWCellArray;
import com.mathworks.toolbox.javabuilder.MWClassID;
import com.mathworks.toolbox.javabuilder.MWException;
import com.mathworks.toolbox.javabuilder.MWNumericArray;

import edu.uestc.gene.model.Statistics;
import edu.uestc.gene.model.Tool;

/**
 * 统计数据分析器，主要有两个函数<br>
 * 1.获取hadamard matrix用于生成正交表<br>
 * 2.对数据进行方差分析
 * 
 * @author Carl
 * 
 *         2013-11-21
 */
public class DataAnalyzer {

	private static DataAnalyzer dataAnalyzer;
	private ANOVA anova;
	private List<Gene> genes;

	// private static Lock instanceLock = new ReentrantLock();

	private DataAnalyzer() throws MWException {
		anova = new ANOVA();
		genes = Statistics.getInstance().getGenes();
	}

	public static synchronized DataAnalyzer getInstance() {
		try {
			// instanceLock.lock();
			if (dataAnalyzer == null) {
				dataAnalyzer = new DataAnalyzer();
			}
		} catch (MWException e) {
			e.printStackTrace();
		}
		// finally {
		// instanceLock.unlock();
		// }
		return dataAnalyzer;
	}

	/**
	 * 返回order * order的正交表<br>
	 * tips:<br>
	 * 1.如果设计饱和则将偏差平方和较小的因素作为误差项<br>
	 * 2.结果返回的矩阵为order * order大小，最后一列用于存放效应值<br>
	 * 3.并将hadamard matrix中-1值改为1，1改为2，以便用于方差分析
	 * 
	 * @param order
	 *            矩阵阶数
	 * @return
	 */
	public int[][] getOrthogonalTable(int order) {
		order = Tool.adjustOrder(order + 1);
		int[][] result = new int[order][order];
		MWNumericArray array = null;
		try {
			array = (MWNumericArray) anova.orthogonalTable(1,
					order * Tool.log2(2))[0];
		} catch (MWException e) {
			e.printStackTrace();
		}
		for (int i = 0; i < order; i++) {
			for (int j = 0; j < order - 1; j++) {
				int value = array.getInt(i * order + j + 2);
				if (value == -1) {
					result[i][j] = 1;
				} else if (value == 1) {
					result[i][j] = 2;
				}
			}
		}
		return result;
	}

	/**
	 * 将数据在第一行添加上因素，以用于方差分析
	 * 
	 * @param factors
	 * @param table
	 * @return
	 */
	public double[][] transfer(double[] factors, int[][] table) {
		double[][] data = new double[table.length + 1][table.length];
		for (int i = 0; i < factors.length; i++) {
			data[0][i] = factors[i];
		}
		// test
		// data[1][3] = 20;
		// data[2][3] = 22;
		// data[3][3] = 30;
		// data[4][3] = 25;
		for (int i = 1; i < data.length; i++) {
			for (int j = 0; j < data[i].length - 1; j++) {
				data[i][j] = table[i - 1][j];
			}
		}
		return data;
	}

	/**
	 * 获得方差分析的结果<br>
	 * 1.分别用0.01和0.05的置信水平区分为“显著”和“非常显著”<br>
	 * 2.如果因素后面加上*符号则表示该因素不显著，作为误差列处理<br>
	 * 3.因素后跟基因序列号，当序列号为0时修改为0.5（由MATLAB限制）<br>
	 * 例:<br>
	 * <table border="1" cellspacing="0" align="left" cellpadding="0" bordercolor=#000000>
	 * <tr>
	 * <td>方差来源</td>
	 * <td>平方和</td>
	 * <td>自由度</td>
	 * <td>均方差</td>
	 * <td>F值</td>
	 * <td>F_alpha</td>
	 * <td>显著性</td>
	 * </tr>
	 * <tr>
	 * <td>因素0.5</td>
	 * <td>[66.6756]</td>
	 * <td>[2]</td>
	 * <td>[33.3378]</td>
	 * <td>[8.4092]</td>
	 * <td>[5.1433;10.9248]</td>
	 * <td>[显著]</td>
	 * </tr>
	 * <tr>
	 * <td>因素1*</td>
	 * <td>[2.0356]</td>
	 * <td>[2]</td>
	 * <td>[1.0178]</td>
	 * <td>[0.2567]</td>
	 * <td>[5.1433;10.9248]</td>
	 * <td>[]</td>
	 * </tr>
	 * <tr>
	 * <td>因素2*</td>
	 * <td>[8.7489]</td>
	 * <td>[2]</td>
	 * <td>[4.3744]</td>
	 * <td>[1.1034]</td>
	 * <td>[5.1433;10.9248]</td>
	 * <td>[]</td>
	 * </tr>
	 * <tr>
	 * <td>空列*</td>
	 * <td>[13.0022]</td>
	 * <td>[2]</td>
	 * <td>[6.5011]</td>
	 * <td>[1.6399]</td>
	 * <td>[5.1433;10.9248]</td>
	 * <td>[]</td>
	 * </tr>
	 * <tr>
	 * <td>误差</td>
	 * <td>[23.7867]</td>
	 * <td>[6]</td>
	 * <td>[3.9644]</td>
	 * <td>[]</td>
	 * <td>[]</td>
	 * <td>[]</td>
	 * </tr>
	 * <tr>
	 * <td>总和</td>
	 * <td>[90.4622]</td>
	 * <td>[8]</td>
	 * <td>[]</td>
	 * <td>[]</td>
	 * <td>[]</td>
	 * <td>[]</td>
	 * </tr>
	 * </table>
	 * <br>
	 * 
	 * @param data
	 * @return
	 */
	public String[][] getANOVA(int numFactors, double[][] data) {
		MWNumericArray array = new MWNumericArray(data, MWClassID.DOUBLE);
		// if (data[0][data[0].length - 2] == 0) {// 非饱和的设计，有空列
		// numFactors++;
		// }
		List<MWArray> resultArray = null;
		try {
			resultArray = ((MWCellArray) anova.opfs(1, array)[0]).asList();
		} catch (MWException e) {
			e.printStackTrace();
		}
		int rows = 0;
		for (int i = 0; i < numFactors + 5; i++) {
			String text = resultArray.get(i).toString();
			if (text.contains("因素")) {// 因素数量
				rows++;
			}
			if (text.contains("空列")) {
				rows++;
			}
		}
		String[][] results = new String[rows + 3][7];
		// int index = 1;
		for (int i = 0; i < results.length; i++) {
			for (int j = 0; j < results[i].length; j++) {
				String text = resultArray.get(i + j * (rows + 3)).toString();
				if (text.contains(".") && !text.contains("因素")
						&& !text.contains(";")) {// 不让显示科学计数法
					text = new BigDecimal(text).toPlainString();
				}
				if (text.contains("因素")) {
					double code = 0;
					if (text.endsWith("*")) {
						code = Double.valueOf(text.substring(2,
								text.length() - 1));
					} else {
						code = Double.valueOf(text.substring(2, text.length()));
					}
					if (code == 0.5) {
						code = 0;
					}
					int eve_code = (int) code;
					Gene code_gene = genes.get(eve_code);
					text = text
							.replaceFirst(eve_code + "", code_gene.getName());
				}
				results[i][j] = text;
			}
		}
		// results = (String[][]) resultArray.toArray();
		return results;
	}

	public static void main(String[] args) {
		int factor = 3;
		DataAnalyzer d = getInstance();
		double[] factors = { 0.5, 1, 2 };
		double[][] r = d.transfer(factors, d.getOrthogonalTable(factor));
		System.out.println("方差分析结果:");
		String[][] result = d.getANOVA(factors.length, r);
		for (int i = 0; i < result.length; i++) {
			for (int j = 0; j < result[i].length; j++) {
				System.out.print(result[i][j] + "\t\t\t");
			}
			System.out.println();
		}
	}
}
